--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 77297e3e3817056e7908922ffd48a6b3d7ffc73e
Parents : b2fcaa4
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-04T23:29:45-05:00
feat(tests): update test suite by adding performance tests and updating test commands to exclude performance hot-paths by default
Changes
4 files changed, 156 insertions(+), 6 deletions(-)
Diff
diff --git a/Makefile b/Makefile
index 430766b0..06282191 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: install run build lint test clean
+.PHONY: install run build lint test test-be-perf clean
install:
pnpm install
@@ -17,7 +17,10 @@ lint:
test:
pnpm run test
- poetry run python -m pytest tests/backend --cov=meshchatx/src/backend
+ poetry run python -m pytest tests/backend --ignore=tests/backend/test_performance_hotpaths.py --cov=meshchatx/src/backend
+
+test-be-perf:
+ poetry run python -m pytest tests/backend/test_performance_hotpaths.py
clean:
rm -rf node_modules build dist python-dist meshchatx/public build-dir out
diff --git a/Taskfile.yml b/Taskfile.yml
index 0cb45e1d..e1a681e4 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -161,14 +161,24 @@ tasks:
- "{{.NPM}} run test:e2e"
test:be:
- desc: Run Python tests (pytest)
+ desc: Run Python tests (pytest; excludes test_performance_hotpaths — use test:be:perf locally)
cmds:
- - poetry run pytest tests/backend --cov=meshchatx/src/backend
+ - poetry run pytest tests/backend --ignore=tests/backend/test_performance_hotpaths.py --cov=meshchatx/src/backend
test:be:cov:
- desc: Run Python tests with detailed coverage
+ desc: Run Python tests with detailed coverage (excludes performance hot-path tests)
+ cmds:
+ - poetry run pytest tests/backend --ignore=tests/backend/test_performance_hotpaths.py --cov=meshchatx/src/backend --cov-report=term-missing
+
+ test:be:perf:
+ desc: Backend performance regression tests (not run in CI; run locally when needed)
cmds:
- - poetry run pytest tests/backend --cov=meshchatx/src/backend --cov-report=term-missing
+ - poetry run pytest tests/backend/test_performance_hotpaths.py
+
+ test:be:full:
+ desc: All backend tests including performance hot paths (local)
+ cmds:
+ - poetry run pytest tests/backend --cov=meshchatx/src/backend
test:fe:
desc: Run frontend tests (vitest)
diff --git a/tests/backend/test_interface_discovery.py b/tests/backend/test_interface_discovery.py
index 2c75cde5..d4376167 100644
--- a/tests/backend/test_interface_discovery.py
+++ b/tests/backend/test_interface_discovery.py
@@ -1,5 +1,7 @@
import json
+import random
import shutil
+import string
import tempfile
from unittest.mock import MagicMock, patch
@@ -353,3 +355,135 @@ async def test_interface_add_includes_discovery_fields(temp_dir):
assert saved["discovery_bandwidth"] == 125000
assert saved["discovery_modulation"] == "LoRa"
assert config.write_called
+
+
+@pytest.mark.asyncio
+async def test_interface_add_discoverable_without_optional_coordinates(temp_dir):
+ config = ConfigDict({"reticulum": {}, "interfaces": {}})
+
+ with (
+ patch("meshchatx.meshchat.generate_ssl_certificate"),
+ patch("RNS.Reticulum") as mock_rns,
+ patch("RNS.Transport"),
+ patch("LXMF.LXMRouter"),
+ ):
+ mock_reticulum = mock_rns.return_value
+ mock_reticulum.config = config
+ mock_reticulum.configpath = "/tmp/mock_config"
+ mock_reticulum.is_connected_to_shared_instance = False
+ mock_reticulum.transport_enabled.return_value = True
+
+ app_instance = ReticulumMeshChat(
+ identity=build_identity(),
+ storage_dir=temp_dir,
+ reticulum_config_dir=temp_dir,
+ )
+
+ add_handler = await find_route_handler(
+ app_instance,
+ "/api/v1/reticulum/interfaces/add",
+ "POST",
+ )
+ assert add_handler
+
+ payload = {
+ "allow_overwriting_interface": False,
+ "name": "NoLatLon",
+ "type": "TCPClientInterface",
+ "target_host": "example.com",
+ "target_port": "4242",
+ "discoverable": "yes",
+ "discovery_name": "Optional coords off",
+ "announce_interval": 360,
+ "reachable_on": "192.0.2.1",
+ }
+
+ class AddRequest:
+ @staticmethod
+ async def json():
+ return payload
+
+ response = await add_handler(AddRequest())
+ data = json.loads(response.body)
+ assert "Interface has been added" in data["message"]
+ saved = config["interfaces"]["NoLatLon"]
+ assert saved["discoverable"] == "yes"
+ assert "latitude" not in saved
+ assert "longitude" not in saved
+ assert "height" not in saved
+ assert config.write_called
+
+
+@pytest.mark.asyncio
+async def test_interface_add_discovery_payload_fuzz_tcp_client(temp_dir):
+ config = ConfigDict({"reticulum": {}, "interfaces": {}})
+
+ with (
+ patch("meshchatx.meshchat.generate_ssl_certificate"),
+ patch("RNS.Reticulum") as mock_rns,
+ patch("RNS.Transport"),
+ patch("LXMF.LXMRouter"),
+ ):
+ mock_reticulum = mock_rns.return_value
+ mock_reticulum.config = config
+ mock_reticulum.configpath = "/tmp/mock_config"
+ mock_reticulum.is_connected_to_shared_instance = False
+ mock_reticulum.transport_enabled.return_value = True
+
+ app_instance = ReticulumMeshChat(
+ identity=build_identity(),
+ storage_dir=temp_dir,
+ reticulum_config_dir=temp_dir,
+ )
+
+ add_handler = await find_route_handler(
+ app_instance,
+ "/api/v1/reticulum/interfaces/add",
+ "POST",
+ )
+ assert add_handler
+
+ for i in range(50):
+ config["interfaces"].clear()
+ config.write_called = False
+
+ name = f"fuzz-{i}-" + "".join(
+ random.choices(string.ascii_letters + string.digits, k=8)
+ )
+ announce = random.randint(5, 1440)
+ lat = random.uniform(-90, 90)
+ lon = random.uniform(-180, 180)
+ height = random.uniform(0, 9000)
+
+ payload = {
+ "allow_overwriting_interface": False,
+ "name": name,
+ "type": "TCPClientInterface",
+ "target_host": "example.com",
+ "target_port": "4242",
+ "discoverable": "yes",
+ "discovery_name": name,
+ "announce_interval": announce,
+ "reachable_on": "192.0.2.1",
+ "latitude": lat,
+ "longitude": lon,
+ "height": height,
+ "discovery_stamp_value": random.randint(1, 32),
+ "discovery_encrypt": bool(random.getrandbits(1)),
+ "publish_ifac": bool(random.getrandbits(1)),
+ }
+
+ class AddRequest:
+ @staticmethod
+ async def json():
+ return payload
+
+ response = await add_handler(AddRequest())
+ data = json.loads(response.body)
+ assert response.status == 200, data
+ assert "Interface has been added" in data["message"]
+ saved = config["interfaces"][name]
+ assert saved["latitude"] == lat
+ assert saved["longitude"] == lon
+ assert saved["height"] == height
+ assert config.write_called
diff --git a/tests/backend/test_performance_hotpaths.py b/tests/backend/test_performance_hotpaths.py
index 2b9ea2fc..45e3d62d 100644
--- a/tests/backend/test_performance_hotpaths.py
+++ b/tests/backend/test_performance_hotpaths.py
@@ -1,5 +1,8 @@
"""Performance regression tests for the critical hot paths.
+Excluded from default `task test:be` / CI; run locally with `task test:be:perf`
+or `task test:be:full` (see Taskfile.yml).
+
Focus areas (user priority):
- NomadNet browser: load announces, search announces, favourites
- Messages: load conversations, search messages, load conversation messages,
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────